<?php

/**
---------------------------------------------------------------------
- 项目: DNS phpsea
- 版本: 1.0
- 文件名:PDOMySQL.class.php
- 原作者:Robot
- 编写日期:2008-12-15
- 简要描述:对PDO的简单封装
- 运行环境:php5.2,mysql版本要 > 5.0
- 修改记录:
- 应用示例:

	$db = new PDOMySQL("mysql:host=localhost;dbname=chuli_passport", "root", "");

---------------------------------------------------------------------
 */

/*
	__construct($dbDSN='mysql',$dbUser='',$dbPass='')	//初始化数据库连接
	exec($sql)
	query($query)                                     //执行sql语句
	getOne($query=null,$x=0,$y=0)                    //获取一个字段
	getRow($query=null,$output=OBJECT,$y=0)          //获取一行记录
	getAll($query=null, $output = OBJECT)        //获取多行记录
*/

//-----------------------------------------------------------------------------
final class PDOMySQL extends PDO
{
	public $total = 0; //结果数
	private $sql = null; //sql
	private $count = 0;
	private $affectedcount = 0;
	private $debuginfo = array(); //调试时记录数据
	private $link;
	static $cpool = array();
	private $dsn = "";
	
	/**
	 * 方法名称 __construct
	 *
	 * @param String DSN = mysql:host=localhost;dbname=test
	 * @param String dbuser
	 * @param String dbpass
	 * @return void
	 */
	public function __construct($db, $table)
	{
		$this->db = $db;
		$this->table = $table;
	}
	
	/**
	 * 方法名称 __construct
	 *
	 * @param String DSN = mysql:host=localhost;dbname=test
	 * @param String dbuser
	 * @param String dbpass
	 * @return void
	 */
	public function connect($user, $pass, $dbDSN) {
		$mark = 0;
		try {
			$this->link = new PDO ( $dbDSN, $user, $pass, array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',PDO::ATTR_TIMEOUT => '3'));
			$mark = 1;
		} catch ( PDOException $e ) {//print_r($e);
			throw new MyException("pdo: db connect failed.");
		}
		return $mark;
	}

	/**
	 * 方法名: exec
	 *
	 * @param String sql
	 * @return Object
	 */
	public final function exec($sql)
	{
		$this->setSql($sql);
		$stmt = $this->link->prepare($sql);
		$stmt->execute();

		//Lost connection to MySQL server during query
		if($this->link->errorCode() == 'HY000')
		{
			$this->setSql($sql);
			$stmt = $this->link->prepare($sql);
			$stmt->execute();
		}

		self::throwSQLError();
		if(defined('SQL_DEBUG_ON') && SQL_DEBUG_ON === true)
		{
			$this->debuginfo[$this->count]['sql'] = $sql;
			$this->debuginfo[$this->count]['affected'] = $stmt->rowCount();
			$this->count += 1;
		}

		return $stmt->rowCount();
	}
	
	/**
	 * 方法名: query
	 *
	 * @param String sql
	 * @return Object
	 */
	public final function query($sql)
	{
		$this->setSql($sql);
		$oQuery = $this->link->query($sql);

		//Lost connection to MySQL server during query
		if($this->link->errorCode() == 'HY000')
		{
			$this->setSql($sql);
			$oQuery = $this->link->query($sql);
		}

		self::throwSQLError();

		if(!is_object($oQuery) && defined('DEBUG_ON') && DEBUG_ON)
		{
			self::showErrSql();
			exit;
		}

		return $oQuery;
	}
	
	/**
	 * 方法名: getLastId
	 *
	 * @param String sql
	 * @return Object
	 */
	public final function getLastId()
	{
		return $this->link->lastInsertID();
	}
	
	/**
	 * 方法名: getOne
	 *
	 * @param String sql
	 * @return Object
	 */
	public final function getOne($sql)
	{
		$oQuery = self::query($sql);
		$sRes = $oQuery->fetchColumn();
		if(defined('SQL_DEBUG_ON') && SQL_DEBUG_ON === true)
		{
			if($oQuery->columnCount() > 0)
				for($i = 0; $i < $oQuery->columnCount(); $i++)
				{
					$tmp = $oQuery->getColumnMeta($i);
					$meta[$i]['name'] = $tmp['name'];
					$meta[$i]['type'] = $tmp['native_type'];
					$meta[$i]['len'] = $tmp['len'];
				}
			$this->debuginfo[$this->count]['sql'] = $sql;
			$this->debuginfo[$this->count]['field'] = $meta;
			$this->debuginfo[$this->count]['data'][0] = $sRes;
			$this->count += 1;
		}
		return $sRes;
	}

	/**
	 * 方法名: prepare
	 * 简要描述:返回指针
	 *
	 * @param String sql
	 * @return Array
	 */
	public function prepare($sql)
	{
		$this->setSql($sql);
		return $this->link->prepare($sql);
	}

	/**
	 * 方法名: getRow
	 * 简要描述:返回一行结果
	 *
	 * @param String sql
	 * @return Array
	 */
	public final function getRow($sql, $sMod = PDO::FETCH_BOTH)
	{
		$oQuery = self::query($sql);
		$sRes = $oQuery->fetch($sMod);
		if(defined('SQL_DEBUG_ON') && SQL_DEBUG_ON === true)
		{
			if($oQuery->columnCount() > 0)
				for($i = 0; $i < $oQuery->columnCount(); $i++)
				{
					$tmp = $oQuery->getColumnMeta($i);
					$meta[$i]['name'] = $tmp['name'];
					$meta[$i]['type'] = $tmp['native_type'];
					$meta[$i]['len'] = $tmp['len'];
				}
			$this->debuginfo[$this->count]['sql'] = $sql;
			$this->debuginfo[$this->count]['field'] = $meta;
			$this->debuginfo[$this->count]['data'][0] = $sRes;
			$this->count += 1;
		}
		return $sRes;
	}
	
	/**
	 * 方法名: getAll
	 * 简要描述:返回全部结果
	 *
	 * @param String sql
	 * @param String PDO::FETCH_ASSOC
	 * @return Array
	 */
	public final function getAlls($sql, $sMod = PDO::FETCH_BOTH)
	{
		if(strpos($sql, 'SQL_CALC_FOUND_ROWS') === false)
		{
			$oQuery = self::query($sql);

			$aRes = $oQuery->fetchAll($sMod);

			if(defined('SQL_DEBUG_ON') && SQL_DEBUG_ON === true)
			{
				if($oQuery->columnCount() > 0)
					for($i = 0; $i < $oQuery->columnCount(); $i++)
					{
						$tmp = $oQuery->getColumnMeta($i);
						$meta[$i]['name'] = $tmp['name'];
						$meta[$i]['type'] = $tmp['native_type'];
						$meta[$i]['len'] = $tmp['len'];
					}
				$this->debuginfo[$this->count]['sql'] = $sql;
				$this->debuginfo[$this->count]['field'] = $meta;
				$this->debuginfo[$this->count]['data'] = $aRes;
				$this->count += 1;
			}
			return $aRes;
		}
		else
		{
			$query = str_replace("SQL_CALC_FOUND_ROWS", "", $sql);
			$oQuery = self::query($query);
			$aRes = $oQuery->fetchAll($sMod);
			
			if(defined('SQL_DEBUG_ON') && SQL_DEBUG_ON === true)
			{
				if($oQuery->columnCount() > 0)
					for($i = 0; $i < $oQuery->columnCount(); $i++)
					{
						$tmp = $oQuery->getColumnMeta($i);
						$meta[$i]['name'] = $tmp['name'];
						$meta[$i]['type'] = $tmp['native_type'];
						$meta[$i]['len'] = $tmp['len'];
					}
				$this->debuginfo[$this->count]['sql'] = $query;
				$this->debuginfo[$this->count]['field'] = $meta;
				$this->debuginfo[$this->count]['data'] = $aRes;
				$this->count += 1;
			}
			
			$sql = preg_replace("/\n|\n\r|\s/", " ", strtolower($sql));
			
			if(strpos($sql, ' order ') === false)
			{
				if(strpos($sql, 'limit') === false)
				{
					preg_match("~select(.*?) from (.*)~ims", $sql, $m);
				}
				else
				{
					preg_match("~select(.*?) from (.*) limit ~ims", $sql, $m);
				}
			}
			else
			{
				preg_match("~select(.*?) from (.*) order ~ims", $sql, $m);
			}
			
			$sql = "SELECT COUNT(*) FROM {$m[2]}";
			$oQuery = self::query($sql);
			//$oQuery = $this->link->query("SELECT FOUND_ROWS()");
			$this->total = $oQuery->fetchColumn();
			
			return $aRes;
		}
	}

	/**
	 * 方法名: 设置sql
	 *
	 * @param String sql
	 * @return Object
	 */
	private function getDsn($masterslave) {
		include CONFIG_DIR.'/db.inc.php';
		if(!$DBSet) {
			throw new MyException("数据库配置不存在.");
		}

		//数据库配置文件检查
		if(empty($DBSet[$this->db]['master'][0]))
		{
			throw new MyException($this->db." not in config file.");
		}

		if ($masterslave == "master") {
			$dbDSN = "mysql:host={$DBSet[$this->db]['master'][0]['host']};port={$DBSet[$this->db]['master'][0]['port']};dbname={$this->db}";
		} else {
			$iNumServer = count($DBSet[$this->db]['slave']);
			$iNode = rand(0, $iNumServer - 1); //初始节点

			$dbDSN = "mysql:host={$DBSet[$this->db]['slave'][$iNode]['host']};port={$DBSet[$this->db]['slave'][$iNode]['port']};dbname={$this->db}";
		}

		$this->dsn_des = $dbDSN;

		$arr = array();
		$arr['user'] = $DBSet['user'];
		$arr['pass'] = $DBSet['pass'];
		$arr['dsn'] = $dbDSN;

		return $arr;
	}

	/**
	 * 方法名: 设置sql
	 *
	 * @param String sql
	 * @return Object
	 */
	public function setSql($sql) {
		$this->sql = $sql;

		//决定主从
		$masterslave = strtolower(substr(trim($this->sql), 0, 6)) == "select" ? "slave" : "master";
		$this->dsn_key = "{$masterslave}/{$this->db}";

		if (!self::$cpool[$this->dsn_key]) {
			$arr = $this->getDsn($masterslave);
			$r = $this->connect($arr['user'], $arr['pass'], $arr['dsn']);
			self::$cpool[$this->dsn_key] = $this->link;
		} else {
			$this->link = self::$cpool[$this->dsn_key];
		}

		$this->ping($masterslave);
	}

	/**
	 * 方法名: 获取sql
	 *
	 * @param String sql
	 * @return Object
	 */
	public final function getSql() {
		return $this->sql;
	}
	
	/**
	 * 方法名: 显示Sql
	 *
	 * @param String sql
	 * @return Object
	 */
	public final function showSql()
	{
		$this->sqlstorage[] = $this->sql;
	}

	/**
	 * 方法名: ping
	 *
	 * @param String sql
	 * @return Object
	 */
	public function ping($masterslave){
		$status = @$this->link->getAttribute(PDO::ATTR_SERVER_INFO);

		//出现连接超时重连
		if(!$status || $status == 'MySQL server has gone away')
		{
			$arr = $this->getDsn($masterslave);
			$this->connect($arr['user'], $arr['pass'], $arr['dsn']);
		}
	}
	
	/**
	 * 方法名: throwSQLError 捕获SQL错误
	 *
	 * @return Object 
	 */
	private final function throwSQLError()
	{
		//记录sql log
		if(defined("SQL_LOG_ON") && SQL_LOG_ON == 'On')
		{
			Common::log($this->getSql(), "sql");
		}

		if($this->link->errorCode() != '00000')
		{
			
			$error = $this->link->errorInfo();

			error_log("SQL Err: [" . $error[2] . "] " . $this->getSql(), 0);
			
			if(defined('SQL_DEBUG_ON') && SQL_DEBUG_ON === false)
			{
				exit();
			}
			
			if(defined('SQL_DEBUG_ON') && SQL_DEBUG_ON)
			{
				self::showErrSql();
			}
		}
	}
	
	/**
	 * 方法名: 显示错误sql
	 * 简要说明：显示调试信息
	 *
	 * @return Object
	 */
	private function showErrSql()
	{
		echo '<meta http-equiv=content-type content="text/html; charset=utf-8">';
		echo "<hr noshade color='#dddddd; size=1><blockquote>";
		print "<font size=2 color='#ff0000'>";
		print "<b>错误语句: </b><br/><br/><font color='#0000ff'> " . $this->getSql() . " </font>";
		print "<br/><br/><b>错误提示: </b> ";
		print "<br/><pre>".var_export(debug_backtrace(), 1)."</pre>";
		print "</blockquote><hr noshade color='#dddddd' size=1>";
		exit();
	}
	
	/**
	 * 方法名: 析构方法
	 * 简要说明：显示调试信息
	 *
	 * @return Object
	 */
	public function __destruct()
	{
		if(defined('SQL_DEBUG_ON') && SQL_DEBUG_ON === true && $this->debuginfo)
		{
			echo "<div style='height:20px;border:2px solid #777;text-align:left;background:#ddd;padding:5px;font-size:12px;color:#800080'><b>Debug..</b>&nbsp;<b>ENV</b>&nbsp;(php " . phpversion() . " - MySQL " . $this->link->getAttribute(PDO::ATTR_SERVER_VERSION) . " - Connect " . $this->dsn_key . " [" . $this->dsn_des . "])</div>";
			
			//输出sql
			if($this->debuginfo)
				foreach($this->debuginfo as $itemkey => $item)
				{
					echo "<div style='text-align:left;border-bottom:1px solid #ccc;margin-top:15px;padding-bottom:8px'>";

					//输出sql
					echo "<font face=arial size=2 color='#000099'><b>查询语句:</b><br/><br/> $item[sql] <br/><br/>";

					echo "<font face=arial size=2 color='#000099'><b>查询结果...</b></font>";
					
					echo "<table cellpadding=5 cellspacing=1 bgcolor='#555555'>";
					echo "<tr bgcolor='#eeeeee'>";
					
					//输出字段
					echo "<td nowrap valign=bottom><font color=555599 face=arial size=2><b>(row)</b></font></td>";
					if($item['field'])
						foreach($item['field'] as $fieldkey => $field)
						{
							echo "<td nowrap align=left valign=top><font size=1 color='#555599' face=arial>{$field[type]} {$field[len]}</font><br><span style='font-family: arial; font-size: 10pt; font-weight: bold;'>{$field[name]}</span></td>";
						}
					
					echo "</tr>";
					
					//输出数据
					if($item['data'][0])
						foreach($item['data'] as $datakey => $data)
						{
							//字符类型
							if(is_string($data))
							{
								echo "<tr bgcolor=ffffff><td bgcolor='#eeeeee' nowrap align=middle><font size=2 color='#555599' face=arial>1</font></td>";
								echo "<td bgcolor='#ffffff' nowrap><font face=arial size=2>$data</font></td>";
								echo "</tr>";
							} //数组类型
							else if(is_array($data))
							{
								echo "<tr bgcolor=ffffff><td bgcolor='#eeeeee' nowrap align=middle><font size=2 color='#ffffff' face=arial>" . ++$datakey . "</font></td>";
								foreach($data as $subdatakey => $data)
								{
									echo "<td bgcolor='#ffffff' nowrap><font face=arial size=2>$data</font></td>";
								}
								echo "</tr>";
							} //对象类型
							else if(is_object($data))
							{
								echo "<tr bgcolor=ffffff><td bgcolor='#eeeeee' nowrap align=middle><font size=2 color='#555599' face=arial>" . ++$datakey . "</font></td>";
								foreach($item['field'] as $fieldkey => $field)
								{
									echo "<td bgcolor='#ffffff' nowrap><font face=arial size=2>" . $data->$field[name] . "</font></td>";
								}
								echo "</tr>";
							}
						}
					else
					{
						$res = isset($item['affected']) ? $item['affected'] : 'No Results';
						echo "<tr bgcolor='#ffffff'><td colspan=" . (count($item[field]) + 1) . "><font face=arial size=2>" . $res . "</font></td></tr>";
					}
					echo "</table>";
					echo "</div>";
				}
		}
	}
}
//=============================================================================
?>
